package com.xiongkf.blockchain.pojo;

import java.util.ArrayList;
import java.util.List;
import com.alibaba.fastjson.JSON;
import com.xiongkf.blockchain.utils.CryptoUtil;

/**
 * 区块链实体类
 * 
 * @author bear.xiong
 */
public class BlockChain {
	// 区块链其实就是一个list
	private List<Block> blockChain;

	/**
	 * 区块链服务
	 * 1、初始化blockchain
	 * 2、区块链加入创世区块
	 */
	public BlockChain() {
		this.blockChain = new ArrayList<Block>();
		blockChain.add(this.getGenesisBlock());
	}

	/**
	 * 获取创世区块
	 * 一般以固定形式返回
	 * 
	 * @return
	 */
	private Block getGenesisBlock() {
		// 1、通过计算方式获得创世区块
		// Block newBlock = new Block();
		// calculateIncludeNonceHash(1, "0", System.currentTimeMillis(), "Hello, I'm First Block!", 0, newBlock);
		// return newBlock;
		// 2、通过固定方式获得创世区块
		return new Block(1, "0", System.currentTimeMillis(), "Hello, I'm First Block!", "0002ae27f80f3a8a9f86a541311451d5e527bd3ceaf2a5a8bc661d7519ca7900", 3469);
	}

	/**
	 * 生成下一个区块
	 * 
	 * @param blockData
	 * @return Block
	 */
	public Block generateNextBlock(String blockData) {
		// 获得前一个区块
		Block previousBlock = this.getLatestBlock();
		Block newBlock = new Block();
		// 区块的索引加1
		int nextIndex = previousBlock.getIndex() + 1;
		// 现在的时间戳
		long nextTimestamp = System.currentTimeMillis();
		long nonce = 0;
		// 计算hash值
		// String nextHash = calculateHash(nextIndex, previousBlock.getHash(), nextTimestamp, blockData);
		// return new Block(nextIndex, previousBlock.getHash(), nextTimestamp, blockData, nextHash);
		calculateIncludeNonceHash(nextIndex, previousBlock.getHash(), nextTimestamp, blockData, nonce, newBlock);
		// 返回生成的新区块
		System.err.println("newBlock:" + JSON.toJSONString(newBlock));

		return newBlock;
	}

	/**
	 * 获取最后一个区块
	 * 
	 * @return 返回区块内容
	 */
	public Block getLatestBlock() {
		return blockChain.get(blockChain.size() - 1);
	}

	/**
	 * 挖矿
	 * 
	 * @param index
	 * @param previousHash
	 * @param timestamp
	 * @param data
	 * @param nonce
	 * @param newBlock
	 * @return
	 */
	private String calculateIncludeNonceHash(int index, String previousHash, long timestamp, String data, long nonce, Block newBlock) {
		String str = index + previousHash + timestamp + data + nonce;
		String hash = CryptoUtil.getSHA256(str);
		do {
			nonce = nonce + 1;
			System.err.println("nonce:" + nonce);
			str = index + previousHash + timestamp + data + nonce;
			hash = CryptoUtil.getSHA256(str);
//			if (isValidHashDifficulty(hash)) {
//			}
			newBlock.setData(data);
			newBlock.setHash(hash);
			newBlock.setIndex(index);
			newBlock.setNonce(nonce);
			newBlock.setPreviousHash(previousHash);
			newBlock.setTimestamp(timestamp);
		} while (!isValidHashDifficulty(hash));// 如果不满足难度系数，那么就继续算

		return hash;
	}

	/**
	 * 检测挖矿操作是否符合难度要求
	 * 
	 * @param hash
	 * @return
	 */
	private boolean isValidHashDifficulty(String hash) {
		// 难度系数
		int dificutty = 4;
		char zero = '0';
		int i;
		for (i = 0; i < hash.length(); i++) {
			char ichar = hash.charAt(i);
			if (ichar != zero) {
				break;
			}
		}

		return i >= dificutty;
	}

	/**
	 * 添加区块
	 * 
	 * @param newBlock
	 */
	public void addBlock(Block newBlock) {
		if (isValidNewBlock(newBlock, getLatestBlock())) {
			blockChain.add(newBlock);
		}
	}

	/**
	 * 共识算法（最基本的共识算法，也就是说符合这四个条件的都能加入到区块链中）：检查区块是否新区块
	 * 
	 * @param newBlock
	 * @param previousBlock
	 * @return true/false
	 */
	private boolean isValidNewBlock(Block newBlock, Block previousBlock) {
		/*
		 * 1、前一个区块的索引加1不等于新区块
		 */
		if (previousBlock.getIndex() + 1 != newBlock.getIndex()) {
			System.err.println(newBlock.getIndex());
			System.out.println("invalid index");
			return false;
		/*
		 * 2、前一个区块的hash不等于新区块中存储的前一个区块的hash
		 */
		} else if (!previousBlock.getHash().equals(newBlock.getPreviousHash())) {
			System.out.println("invalid previoushash");
			return false;
		} else {
			// 计算hash
			// String hash = calculateHash(newBlock.getIndex(), newBlock.getPreviousHash(), newBlock.getTimestamp(), newBlock.getData());
			String hash = calculateIncludeNonceHash(newBlock.getIndex(), newBlock.getPreviousHash(), newBlock.getTimestamp(), newBlock.getData(), newBlock.getNonce(), newBlock);
			/*
			 * 3、重新计算新区块的hash是否和之前算过区块的hash值相等
			 */
			if (!hash.equals(newBlock.getHash())) {
				System.out.println("invalid hash: " + hash + " " + newBlock.getHash());
				return false;
			}
			// 4、验证难度系数（去掉了）
		}

		return true;
	}

	/**
	 * 2个节点：
	 * 区块链1长度100
	 * 区块链2长度101
	 * 新区块链替换成+1的区块链
	 * 
	 * @param newBlocks
	 */
	public void replaceChain(List<Block> newBlocks) {
		// 如果新区块链合法并且新区块链的长度大于现有区块链的长度，则替换
		if (isValidBlocks(newBlocks) && newBlocks.size() > blockChain.size()) {
			blockChain = newBlocks;
		} else {
			System.out.println("Received blockchain invalid");
		}
	}

	/**
	 * 验证区块链是否合法
	 * 
	 * @param newBlocks
	 * @return true/false
	 */
	private boolean isValidBlocks(List<Block> newBlocks) {
		// 获得第一个区块链
		Block fristBlock = newBlocks.get(0);

		// 判断第一个区块是否是创世区块
		if (fristBlock.equals(getGenesisBlock())) {
			return false;
		}

		// 循环每个验证区块是否合法
		for (int i = 1; i < newBlocks.size(); i++) {
			if (isValidNewBlock(newBlocks.get(i), fristBlock)) {
				fristBlock = newBlocks.get(i);
			} else {
				return false;
			}
		}

		return true;
	}

	/**
	 * 获得区块链
	 * 
	 * @return
	 */
	public List<Block> getBlockChain() {
		return blockChain;
	}

	public static void main(String[] args) {
		BlockChain bc = new BlockChain();

		Block secondBlock = bc.generateNextBlock("second Block");
		bc.addBlock(secondBlock);

		System.err.println("secondBlock-->" + JSON.toJSONString(secondBlock));
		Block threeBlock = bc.generateNextBlock("{\"order\":\"111111\"}");
		System.err.println("threeBlock-->" + JSON.toJSONString(threeBlock));
		bc.addBlock(threeBlock);
		List<Block> blockchains = bc.getBlockChain();

		System.err.println("blockchain--->" + JSON.toJSONString(blockchains));
		// System.err.println(bc.isValidHashDifficulty("0002ae27f80f3a8a9f86a541311451d5e527bd3ceaf2a5a8bc661d7519ca7900"));
		Block newBlock = new Block();
		System.err.println(bc.calculateIncludeNonceHash(1, "0", System.currentTimeMillis(), "Hello, I'm First Block!", 0, newBlock));
		// System.err.println(JSON.toJSONString(bc.getGenesisBlock()));
	}
}
